
'use strict';

// 图片处理模块
const mapper = require('./face_mapper');
const TypeDef = require('./typedef');
const MError = require('./merror');
const libc = require('./libc');
const config = require('./config');
const m = require('./logger');
const ref = TypeDef.Ref;

class ArcFace {
    // 构造函数
    constructor(libFile) {
        try {
            this.mapper = mapper(libFile);
        }catch (e) {
            m.logger.error("initLib error:%s",e.toString());
        }
        this.pointers = [];
        this.overEngine = null;
    }

    // 获取文件版本
    getVersion(){
        try {
            let version = this.mapper.ASFGetVersion();

            return  {
                Version: version.Version,
                BuildDate: version.BuildDate,
                CopyRight: version.CopyRight,
            };
        }catch (e) {
            m.logger.error("getVersion error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    // 获取激活文件信息
    getActiveFileInfo(){
        try{
            let active = new TypeDef.ASF_ActiveFileInfo();
            let r = this.mapper.ASFGetActiveFileInfo(active.ref());

            if (r !== 0){
                m.logger.error("get active file info failed, for code %d",r);
                this.unInitialEngine();
                return null;
            }
            m.logger.info("get active file info success, for code %d",r);
            let _active = {
                startTime: active.startTime,
                endTime: active.endTime,
                platform: active.platform,
                sdkType: active.sdkType,
                appId: active.appId,
                sdkKey: active.sdkKey,
                sdkVersion: active.sdkVersion,
                fileVersion: active.fileVersion,
            };
            return _active;
        }catch (e) {
            m.logger.error("getActiveFileInfo error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    // 在线激活
    activeFaceEngine(){
        try {
            let r = this.mapper.ASFOnlineActivation(config.appId, config.appKey);
            if (r !== TypeDef.MOK && r !== MError.MERR_ASF_ALREADY_ACTIVATED) {
                m.logger.error("active engine failed,Code:%d", r);
                return false;
            }
            m.logger.info("active engine success,Code:%d", r);
            return true;
        }catch (e) {
            m.logger.error("activeFaceEngine error:%s",e.toString());
            return false;
        }
    }

    // 在线激活接口，该接口与ASFOnlineActivation接口功能一致，推荐使用该接口
    activeFaceEngineOnline(){
        try{
            let r = this.mapper.ASFActivation(config.appId,config.appKey);
            if (r !== TypeDef.MOK &&  r !== MError.MERR_ASF_ALREADY_ACTIVATED){
                m.logger.error("激活引擎失败,Code:%d",r);
            }
            m.logger.info("激活引擎成功,Code:%d",r);
        }catch (e) {
            m.logger.error("activeFaceEngineOnline error:%s",e.toString());
        }
    }

    // 初始化引擎
    initialFaceEngine(minFaceSize,maxFaceNum){
		try{
			let engine = ref.alloc(TypeDef.MHandle);
			let ret = this.mapper.ASFInitEngine(
                config.video === 1?TypeDef.ASF_DETECT_MODE_VIDEO:TypeDef.ASF_DETECT_MODE_IMAGE,
			    TypeDef.ASF_OP_ALL_OUT,
			    minFaceSize ,
			    maxFaceNum ,
			    TypeDef.ASF_FACE_DETECT|TypeDef.ASF_FACERECOGNITION|TypeDef.ASF_FACE3DANGLE|TypeDef.ASF_LIVENESS|TypeDef.ASF_IR_LIVENESS|TypeDef.ASF_AGE|TypeDef.ASF_GENDER,
			    engine
			);
			if (ret !== 0) {
			    m.logger.error('initial face engine failed,ret-code: %d',ret);
                this.unInitialEngine();
			    return;
			}
			// 全局
			this.overEngine = engine.deref();
			m.logger.info('initial face engine success,ret-code: %d',ret);
		}catch(e){
			m.logger.error("initialFaceEngine error:%s",e.toString());
		}
    }

    // 释放全局指针，防止报错后内存溢出
    freePointers() {
        this.pointers.forEach(ptr => !ptr.isNull() && libc.free(ptr));
        this.pointers = [];
    }

    // 释放操作指针数组
    free(points) {
        points.forEach(ptr => !ptr.isNull() && libc.free(ptr));
    }

    // 释放单个指针，根据需要操作
    release(ptr){
        libc.free(ptr);
    }

    // 销毁引擎
    unInitialEngine(){
        try{
            if (!!this.overEngine && !this.overEngine.isNull()) {
                const ret = this.mapper.ASFUninitEngine(this.overEngine);
                if (ret !== 0) {
                    m.logger.error('destroy face engine failed,ret-code: %d',ret);
                    return;
                }
                this.freePointers();
                m.logger.info('destroy face engine success,ret-code: %d',ret);
            }
        }catch (e) {
            m.logger.error("unInitialEngine error:%s",e.toString());
        }

    }

    // 设置活体置信度
    setLiveParam(hold_rgb,hold_ir){
        try{
            if(!this.checkEngine()){
                return;
            }
            let liveHold = new TypeDef.ASF_LivenessThreshold;
            liveHold.thresholdmodel_BGR = hold_rgb;
            liveHold.thresholdmodel_IR = hold_ir;
            let ret = this.mapper.ASFSetLivenessParam(
                this.overEngine,
                liveHold.ref()
            );
            if(ret !== 0){
                this.unInitialEngine();
                m.logger.info('set live failed,ret-code: %d',ret);
                return;
            }
            m.logger.info('set live success,ret-code: %d',ret);
        }catch (e) {
            m.logger.error("setLiveParam error:%s",e.toString());
        }
    }

    // 人脸检测
    detectFaces(avsl){
        try{
            if(!this.checkEngine()){
                return null;
            }

            // buffer 即 MUInt8*  即 buffer 即 指针
            let multiFaceInfo = new TypeDef.ASF_MultiFaceInfo();
            let detectCode = this.mapper.ASFDetectFaces(
                this.overEngine,
                avsl.width,
                avsl.height,
                TypeDef.ASVL_PAF_RGB24_B8G8R8,
                avsl.imageBuffer,
                multiFaceInfo.ref(),
                TypeDef.ASF_DETECT_MODEL_RGB
            );

            if (detectCode !== 0) {
                m.logger.error('face detect failed,ret-code: %d',detectCode);
                this.unInitialEngine();
                return;
            }

            m.logger.info('face detect success,ret-code: %d',detectCode);
            let multi = this.getFaceInfo(multiFaceInfo);

            return {
                data: avsl,
                multi: multi,
                info: multiFaceInfo,
            };
        }catch (e) {
            m.logger.error("detectFaces error:%s",e.toString());
            return null;
        }
    }

    // 人脸检测
    detectFacesEx(asvl){
        try{
            if(!this.checkEngine()){
                return null;
            }
            let multiFaceInfo = new TypeDef.ASF_MultiFaceInfo();
            let ret = this.mapper.ASFDetectFacesEx(
                this.overEngine,
                asvl.ref(),
                multiFaceInfo.ref(),
                TypeDef.ASF_DETECT_MODEL_RGB
            );

            if (ret !== 0) {
                this.unInitialEngine();
                m.logger.error('face detect failed,ret-code: %d',ret);
                return null;
            }
            m.logger.info('face detect success,ret-code: %d',ret);
            let multi = this.getFaceInfo(multiFaceInfo);

            return {
                data: asvl,
                multi: multi,
                info: multiFaceInfo,
            };
        }catch (e) {
            m.logger.error("detectFacesEx error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    // 获取人脸数据
    getFaceInfo(faces){
        if (!faces.faceNum || faces.faceNum === 0){
            return null;
        }

        const rect = new TypeDef.MRECT();
        const orientBuf = new Buffer(TypeDef.ASF_OrientCode.size);

        let multi = {
            faceRect: [],
            faceOrient: [],
            faceNum: faces.faceNum,
            faceID:[],
        };

        for (let i = 0; i < faces.faceNum; ++i) {
            libc.memcpy(rect.ref().address(), faces.faceRect.address() + i * TypeDef.MRECT.size, TypeDef.MRECT.size);
            libc.memcpy(orientBuf.address(), faces.faceOrient.address() + i * TypeDef.ASF_OrientCode.size, TypeDef.ASF_OrientCode.size);

            multi.faceRect.push({
                left: rect.left,
                top: rect.top,
                right: rect.right,
                bottom: rect.bottom
            });
            multi.faceOrient.push(ref.get(orientBuf, 0, TypeDef.ASF_OrientCode));
        }
        m.logger.info('get face info execute success...');
        return multi;
    }

    // 提取特征值
    extractFeature(avsl, rect, orient) {
        try{
            if(!this.checkEngine()){
                return null;
            }
            m.logger.info('begin extract face feature.');
            if(rect == null){
                m.logger.error('rect parameter is null,please check.');
                // this.unInitialEngine();
                return null;
            }
            if(orient == null){
                m.logger.error('orient parameter is null,please check.');
                // this.unInitialEngine();
                return null;
            }

            let single = new TypeDef.ASF_SingleFaceInfo();
            single.faceOrient = orient;
            single.faceRect.left = rect.left;
            single.faceRect.top = rect.top;
            single.faceRect.right = rect.right;
            single.faceRect.bottom = rect.bottom;

            let feature = new TypeDef.ASF_FaceFeature();
            let ret = this.mapper.ASFFaceFeatureExtractEx(this.overEngine, avsl.ref(), single.ref(), feature.ref());
            if (ret !== 0) {
                m.logger.error('extract face feature failed,ret-code: %d.',ret);
                // this.unInitialEngine();
                return null;
            }
            m.logger.info('extract face feature success,ret-code: %d.',ret);


            // 特征值写在内存固定地址，需要拷贝到新地址存储
            const _feature = new TypeDef.ASF_FaceFeature();
            _feature.featureSize = feature.featureSize;
            _feature.feature = libc.malloc(_feature.featureSize);
            this.pointers.push(_feature.feature);
            libc.memset(_feature.feature.address(),0,_feature.featureSize);
            libc.memcpy(_feature.feature.address(), feature.feature.address(), _feature.featureSize);

            let _normal = {
                featureSize: _feature.featureSize,
                feature: [],
                featureBuffer:'',
            };

            const arr = new Buffer(TypeDef.MByte.size);
            for (let i = 0; i < _feature.featureSize; i++) {
                libc.memcpy(arr.address(), feature.feature.address() + i * TypeDef.MByte.size, TypeDef.MByte.size);
                this.pointers.push(feature.feature);
                _normal.feature.push(ref.get(arr, 0, TypeDef.MByte));
            }

            let buf = new Buffer(_feature.featureSize);
            for (let i = 0; i < _feature.featureSize; ++i) {
                buf[i] = _normal.feature[i];
            }
            _normal.featureBuffer = Buffer.from(buf).toString('base64');
            // this.base64ToFeature( _normal.featureBuffer);
            return {
                nav: _feature,
                nab: _normal,
            };
        }catch (e) {
            m.logger.error("extractFeature error:%s",e.toString());
            this.unInitialEngine();
            return  null;
        }
    }

    // 将特征值转为Feature
    base64ToFeature(buf){
        try{
            let buffers = new Buffer(buf, 'base64');
            if (buffers.length !== 1032){
                m.logger.error('base64ToFeature error,buffers length is not enough');
                this.unInitialEngine();
                return null;
            }
            // 定义 struct
            const _feature = new TypeDef.ASF_FaceFeature();
            _feature.featureSize = buffers.length;
            _feature.feature = libc.malloc(buffers.length);

            // 定义 中间buffer
            const arr = new Buffer(TypeDef.MByte.size);
            for (let i = 0; i < buffers.length; i++) {
                arr[i] = buffers[i]
            }

            // 拷贝地址 指针偏移即取到数据
            for (let i = 0; i < buffers.length; i++) {
                libc.memcpy(_feature.feature.address()+ i * TypeDef.MByte.size, arr.address() , TypeDef.MByte.size);
                this.pointers.push(arr);
            }
            // 返回数据，arr不能清空，共享内存,使用完后统一清空
            return _feature;
        }catch (e) {
            m.logger.error("base64ToFeature error:%s",e.toString());
            this.unInitialEngine();
            return  null;
        }
    }

    // 比对人脸数据
    compareFeature(feature1, feature2) {
        try{
            if(!this.checkEngine()){
                return -2;
            }
            let similar = ref.alloc(TypeDef.MFloat);
            let ret = this.mapper.ASFFaceFeatureCompare(this.overEngine, feature1.ref(), feature2.ref(), similar,TypeDef.ASF_LIFE_PHOTO);

            if (ret !== 0) {
                m.logger.error('compare face feature failed,ret-code: %d.',ret);
                this.unInitialEngine();
                return -1;
            }
            m.logger.info('compare face feature success,ret-code: %d.similar: %f',ret,similar.deref());
            return similar.deref();
        }catch (e) {
            m.logger.error("compareFeature error:%s",e.toString());
            this.unInitialEngine();
            return -1;
        }
    }

    // 检查引擎是否为空
    checkEngine(){
        if (this.overEngine == null || this.overEngine.isNull()) {
            m.logger.error('for engine is null.');
            return false;
        }
        return true;
    }

    // 人脸信息提取
    processNone(avsl,isIR){
        try{
            if(!this.checkEngine()){
                return false;
            }
            let multiFaceInfo = this.detectFaces(avsl);
            // console.log('multiFaceInfo',multiFaceInfo);
            if (!multiFaceInfo || !multiFaceInfo.multi){
                m.logger.error('face process failed for detect faces failed');
                this.unInitialEngine();
                return false;
            }
            let ret = -1;
            if(!isIR){
                m.logger.info('face process is doing not IR detect');
                ret = this.mapper.ASFProcess(
                    this.overEngine,
                    avsl.width,
                    avsl.height,
                    TypeDef.ASVL_PAF_RGB24_B8G8R8,
                    multiFaceInfo.data.imageBuffer,
                    multiFaceInfo.info.ref(),
                    TypeDef.ASF_FACE3DANGLE|TypeDef.ASF_LIVENESS|TypeDef.ASF_AGE|TypeDef.ASF_GENDER
                );
            } else {
                m.logger.info('face process is doing IR detect');
                ret = this.mapper.ASFProcess_IR(
                    this.overEngine,
                    avsl.width,
                    avsl.height,
                    TypeDef.ASVL_PAF_GRAY,
                    avsl.muint8,
                    multiFaceInfo.ref(),
                    TypeDef.ASF_FACE3DANGLE|TypeDef.ASF_LIVENESS|TypeDef.ASF_AGE|TypeDef.ASF_GENDER
                );
            }

            if (ret !== 0) {
                m.logger.error('face process failed,ret-code: %d',ret);
                this.unInitialEngine();
                return false;
            }
            m.logger.info('face process success,ret-code: %d',ret);
            // console.log("multiFaceInfo",multiFaceInfo);
            return true;
        }catch (e) {
            m.logger.error("processEx error:%s",e.toString());
            this.unInitialEngine();
            return false;
        }
    }

    // 人脸信息提取
    processEx(avsl,multiFaceInfo,isIR,isDetect){
        try{
            if(!this.checkEngine()){
                return false;
            }

            if (isDetect){
                multiFaceInfo = this.detectFacesEx(avsl);
                if (!multiFaceInfo || !multiFaceInfo.multi){
                    m.logger.error('detect faces,face process failed for detect faces failed');
                    // this.unInitialEngine();
                    return false;
                }
            }else{
                if (!multiFaceInfo || !multiFaceInfo.multi){
                    m.logger.error('none detect faces,face process failed for detect faces failed');
                    // this.unInitialEngine();
                    return false;
                }
            }

            let ret = -1;
            if(!isIR){
                m.logger.info('face process is doing not IR detect');
                ret = this.mapper.ASFProcessEx(
                    this.overEngine,
                    avsl.ref(),
                    multiFaceInfo.info.ref(),
                    TypeDef.ASF_FACE3DANGLE|TypeDef.ASF_LIVENESS|TypeDef.ASF_AGE|TypeDef.ASF_GENDER
                );
            } else {
                m.logger.info('face process is doing IR detect');
                ret = this.mapper.ASFProcessEx_IR(
                    this.overEngine,
                    avsl.ref(),
                    multiFaceInfo.info.ref(),
                    TypeDef.ASF_FACE3DANGLE|TypeDef.ASF_LIVENESS|TypeDef.ASF_AGE|TypeDef.ASF_GENDER
                );
            }


            if (ret !== 0) {
                m.logger.error('face process failed,ret-code: %d',ret);
                // this.unInitialEngine();
                return false;
            }
            m.logger.info('face process success,ret-code: %d',ret);
            // console.log("multiFaceInfo",multiFaceInfo);
            return true;
        }catch (e) {
            m.logger.error("processEx error:%s",e.toString());
            this.unInitialEngine();
            return false;
        }
    }

    // 获取性别信息
    getSexInfo(){
        try{
            if(!this.checkEngine()){
                return null;
            }
            let sexInfo = new TypeDef.ASF_GenderInfo();
            let ret = this.mapper.ASFGetGender(this.overEngine,sexInfo.ref());

            if (ret !== 0) {
                m.logger.error('get face sex failed,ret-code: %d',ret);
                this.unInitialEngine();
                return null;
            }
            m.logger.info('get face sex success,ret-code: %d',ret);
            const genderArrayBuff = new Buffer(TypeDef.MInt8.size);
            let _sex = {
                genderArray: [],
                num: sexInfo.num
            };

            for (let i = 0; i < _sex.num; ++i) {
                libc.memcpy(genderArrayBuff.address(), sexInfo.genderArray.address() + i * TypeDef.MInt8.size, TypeDef.MInt8.size);
                this.pointers.push(sexInfo.genderArray);
                _sex.genderArray.push(ref.get(genderArrayBuff, 0, TypeDef.MInt8));
            }
            m.logger.info('get face sex success,ret-info: \n%o.',_sex);
            return _sex;
        }catch (e) {
            m.logger.error("getSexInfo error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    // 获取年龄信息
    getAgeInfo(){
        try{
            if(!this.checkEngine()){
                return null;
            }
            let ageInfo = new TypeDef.ASF_AgeInfo();
            let ret = this.mapper.ASFGetAge(this.overEngine,ageInfo.ref());

            if (ret !== 0) {
                m.logger.error('get face age failed,ret-code: %d',ret);
                this.unInitialEngine();
                return null;
            }
            m.logger.info('get face age success,ret-code: %d',ret);

            const ageArrayBuff = new Buffer(TypeDef.MInt8.size);
            let _age = {
                ageArray: [],
                num: ageInfo.num
            };

            for (let i = 0; i < _age.num; ++i) {
                libc.memcpy(ageArrayBuff.address(), ageInfo.ageArray.address() + i * TypeDef.MInt8.size, TypeDef.MInt8.size);
                this.pointers.push(ageInfo.ageArray);
                _age.ageArray.push(ref.get(ageArrayBuff, 0, TypeDef.MInt8));
            }
            m.logger.info('get face age success,ret-info: \n%o.',_age);
            return _age;
        }catch (e) {
            m.logger.error("getAgeInfo error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    // 获取角度信息
    getAngleInfo(){
        try{
            if(!this.checkEngine()){
                return null;
            }
            let angleInfo = new TypeDef.ASF_Face3DAngle;
            let ret = this.mapper.ASFGetFace3DAngle(this.overEngine,angleInfo.ref());

            if (ret !== 0) {
                m.logger.error('get face angle failed,ret-code: %d',ret);
                this.unInitialEngine();
                return null;
            }
            m.logger.info('get face angle success,ret-code: %d',ret);

            const rollArrayBuff = new Buffer(TypeDef.MFloat.size);
            const yawArrayBuff = new Buffer(TypeDef.MFloat.size);
            const pitchArrayBuff = new Buffer(TypeDef.MFloat.size);
            const statusArrayBuff = new Buffer(TypeDef.MInt8.size);

            let _angle = {
                roll: [],
                yaw: [],
                pitch: [],
                status: [],
                num: angleInfo.num
            };

            for (let i = 0; i < _angle.num; ++i) {
                libc.memcpy(rollArrayBuff.address(), angleInfo.roll.address() + i * TypeDef.MFloat.size, TypeDef.MFloat.size);
                this.pointers.push(angleInfo.roll);
                _angle.roll.push(ref.get(rollArrayBuff, 0, TypeDef.MFloat));

                libc.memcpy(yawArrayBuff.address(), angleInfo.yaw.address() + i * TypeDef.MFloat.size, TypeDef.MFloat.size);
                this.pointers.push(angleInfo.yaw);
                _angle.yaw.push(ref.get(yawArrayBuff, 0, TypeDef.MFloat));

                libc.memcpy(pitchArrayBuff.address(), angleInfo.pitch.address() + i * TypeDef.MFloat.size, TypeDef.MFloat.size);
                this.pointers.push(angleInfo.pitch);
                _angle.pitch.push(ref.get(pitchArrayBuff, 0, TypeDef.MFloat));

                libc.memcpy(statusArrayBuff.address(), angleInfo.status.address() + i * TypeDef.MInt8.size, TypeDef.MInt8.size);
                this.pointers.push(angleInfo.status);
                _angle.status.push(ref.get(statusArrayBuff, 0, TypeDef.MInt8));
            }
            m.logger.info('get face angle success,ret-info: \n%o.',_angle);
            return _angle;
        }catch (e) {
            m.logger.error("getAngleInfo error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }

    //获取RGB活体结果
    getLivenessScore(isIR) {
        if(!this.checkEngine()){
            return null;
        }
        try {
            let ret = -1;
            let liveInfo = new TypeDef.ASF_LivenessInfo;
            if (isIR){
                ret = this.mapper.ASFGetLivenessScore_IR(this.overEngine,liveInfo.ref());
            }else{
                ret = this.mapper.ASFGetLivenessScore(this.overEngine,liveInfo.ref());
            }

            if (ret !== 0) {
                m.logger.error('get face live score failed,ret-code: %d,IR: %d',ret,isIR);
                this.unInitialEngine();
                return null;
            }
            m.logger.info('get face live score success,ret-code: %d,IR: %d',ret,isIR);

            const isLiveArrayBuff = new Buffer(TypeDef.MInt8.size);
            let _live = {
                isLive: [],
                num: liveInfo.num
            };

            for (let i = 0; i < _live.num; ++i) {
                libc.memcpy(isLiveArrayBuff.address(), liveInfo.isLive.address() + i * TypeDef.MInt8.size, TypeDef.MInt8.size);
                this.pointers.push(liveInfo.isLive);
                _live.isLive.push(ref.get(isLiveArrayBuff, 0, TypeDef.MInt8));
            }
            m.logger.info('get face sex success,IR: %d,ret-info: \n%o.',isIR,_live);
            return _live;
        } catch (e) {
            m.logger.error("getLivenessScore error:%s",e.toString());
            this.unInitialEngine();
            return null;
        }
    }
}

ArcFace.mapper =  mapper;
ArcFace.Lib = libc;
ArcFace.TypeDef = TypeDef;

// es6写法，统一为此写法
export default ArcFace
// node写法
// module.exports = {ArcFace};
